# -*- coding: utf-8 -*-
#
#    Copyright (C) 2012 Gonzalo Bonigo <gonzalobonigo@gmail.com>
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see http://www.gnu.org/licenses/
#


"""
    Este modulo contiene la definicion de las clases que reprecentan estados en un
    LTS, Diagnoser o TwinPlant.
"""

__version__ = 3.1

from error import LTSError

class State:
    """
        Esta clase representa un estado de un LTS.
    
        Las instancias de State() sirven para repcentar estados en un lts,
        en un diagnoser y en una twin-plant. La forma de diferenciar a cual de
        estos tipos de sistemas pertence un estado es de acuerdo a la
        informacion que este contiene. Por ejemplo, un estado de un lts deberia
        tener con valor 'None' los atributos 'fault_info', 'left_state' y
        'right_statet'; un estado de un diagnoser deberia tener con valor 'Noen'
        los atributos 'left_state' y 'right_statet'; y un estado de una
        twin-plant deberia tener todos estos atributos con algun valor. Otra
        forma es mirando la reprecentacion en un string del estado, ya que para
        cada uno de los tres tipos de sistemas los estados tiene una
        reprecentacion diferente como cadena de caracteres.
    
        Si bien no es necesario tener metodos para acceder o modificar los
        atributos publicos de una clase, en este caso necesitamos que para
        ciertos atributos tales como 'name', 'init', y 'fault_info' estos
        metodos existan, pues hay funciones que necesitan como argumento un
        metodo que medifique estos atributos.
    """
    
    ### Atributos.
    
    # Nombre del estado.
    name = ''
    # Dice si es el estado inicial o no.
    init = False
    # Informacion de fallas
    fault_info = None
    # Informacion de estados (Izquierdo,Derecho).
    left_state = None
    right_state = None
    # Dice si fue visitado o no.
    visited = False
    
    def __init__ (self, name='', init=False):
        # Relacion de vecino. Es un conjunto de tuplas=(event,state).
        self._neighbors = set()
        self.name = name
        self.init = init
    
    def __eq__ (self, y):
        result = False
        if y == None:
            result = False
        else:
            result = (self.fault_info == y.fault_info) and \
                     (self.left_state == y.left_state) and \
                     (self.right_state==y.right_state) and (self.name==y.name)
        return result
    
    def __str__ (self):
        result = self.name
        if self.left_state!=None and self.right_state!=None:
            result += '#L('
            if self.left_state.fault_info:
                for f in self.left_state.fault_info:
                    if result.endswith('('):
                        result += f
                    else:
                        result += ','+f
            result += ')#R('
            if self.right_state.fault_info:
                for f in self.right_state.fault_info:
                    if result.endswith('('):
                        result += f
                    else:
                        result += ','+f
            result += ')'
        elif self.fault_info != None:
            result += '#('
            for f in self.fault_info:
                if result.endswith('('):
                    result += f
                else:
                    result += ','+f
            result += ')'
        else:
            pass
        return result
    
    def __hash__ (self):
        return self.name.__hash__()
    
    ### Metodos para la relacion de vecino.
    
    def is_neighbor (self, neighbor, event=None):
        """
            Descripcion:
                Dice si un estado esta relacionado con 'self', es decir, dice si
                un estado pertence a la relacion de vecino de 'self' o no.
                Mas formalmente: self.is_neighbor(n,e)=True <==> Exist (n',e')
                in self._neighbors | n=n' and (e=e' or e=None).
                Si el argumento 'event' es especificado, nos dice si un estado
                esta relacionado con 'self' con evento 'event'.
            Argumentos:
                neighbor  -  Una instancia de la clase State() de la cual se
                             quiere saber si esta relacionada con 'self'.
                event     -  Opcional. Es una cadena de caracteres que representa 
                             un estado. Si este arguemtno se especifica entonces
                             se busca si existe tal relacion pero con este evento.
            Retorno:
                True   -  Si 'neighbor' esta relacionado con 'self'.
                False  -  En caso contrario.
        """
        xs = [t[1] for t in self._neighbors \
                if t[1]==neighbor and (not event or t[0]==event)]
        return neighbor in xs
    
    def add_neighbor (self, event, neighbor):
        """
            Descripcion:
                Agrega un estado a la relacion de vecinos.
            Argumentos:
                event     -  Es una cadena de carateres que representa el
                             evento con el cual se agrega la transicion.
                neighbor  -  Una instancia de la clase State() la cual se
                             quiere agregar a la relacion de vecino.
            Retorno:
                Ninguno.
    
        """
        self._neighbors.add((event,neighbor))
    
    def remove_neighbor (self, event, neighbor):
        """
            Descripcion:
                Elimina un estado a la relacion de vecinos.
            Argumentos:
                event     -  Es una cadena de carateres que representa el
                             evento de la transicion a eliminar.
                neighbor  -  Una instancia de la clase State() la cual se
                             quiere eliminar a la relacion de vecino.
            Retorno:
                Ninguno.
        """
        self._neighbors.remove((event,neighbor))
    
    def get_neighbor_set (self):
        """
            Descripcion:
                Devuelve el conjunto de relacion de vecinos.
                NOTA: Devuelve una referencia al conjunto, no una copia de el.
            Argumentos:
                Nunguno.
            Retorno:
                Una instancia de la clase Set() que es el conjunto de vecinos
                de 'self'.
        """
        return self._neighbors
    
    def reset_neighbor_set (self):
        """
            Descripcion:
                Elimina todos los elementos de la relacion de vecino.
            Argumentos:
                Ninguno.
            Retorno:
                Ninguno.
        """
        self._neighbors = set()
    
    ### Metodos para el nombre.
    
    def set_name (self, name):
        """
            Descripcion:
                Establece el nombre de un estado.
            Argumentos:
                name  -  Una cadena de caracteres que reprecente el nombre
                         del estado.
            Retorno:
                Ninguno.
        """
        self.name = name
    
    def get_name (self):
        """
            Descripcion:
                Devuelve el nombre del estado.
            Argumentos:
                Ninguno.
            Retorno:
                Una cadena de caracteres que representa el nombre del estado.
        """
        return self.name
    
    ### Metodos para el indicador de estado incial.
    
    def set_init_state (self, init):
        """
            Descripcion:
                Establece si es o no el estado inicial.
            Argumentos:
                init  -  Un booleano que indica si el estado es inicial o no.
            Retorno:
                Ninguno.
        """
        self.init = init
    
    def is_init_state (self):
        """
            Descripcion:
                Dice si el estado es un estado inicial o no.
            Argumentos:
                Ninguno.
            Retorno:
                True   -  Si el estado es inicial.
                False  -  Si no lo es.
        """
        return self.init
    
    ### Metodos para la informacion de fallas.
    
    def add_fault_info (self, fault):
        """
            Descripcion:
                Agregala una falla al conjunto de fallas del estado.
            Argumentos:
                fault  -  Una cadena de caracteres que representa una falla.
            Retorno:
                Ninguno.
        """
        if self.fault_info == None:
            self.fault_info = set()
        self.fault_info.add(fault)
    
    def get_fault_info (self):
        """
            Descripcion:
                Devuelve el conjunto de fallas del estado
                NOTA: Devuelve una referencia al conjunto, no una copia de el.
            Argumentos:
                Nunguno.
            Retorno:
                Una instancia de la clase Set() que es el conjunto de fallas.
        """
        if self.fault_info != None:
            return self.fault_info
        else:
            return set()
